package per.mnxc.excel.tree.util;


import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.img.ColorUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.TypeUtil;
import cn.hutool.json.JSONNull;
import cn.hutool.poi.excel.cell.CellUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;
import per.mnxc.excel.tree.annotation.ExcelNode;
import per.mnxc.excel.tree.constants.TextConst;
import per.mnxc.excel.tree.exception.TreeExportException;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * @author jiangao
 * @since 2025/1/3
 */

@Data
public class ExcelTreeUtil<T> {

    private Type type;

    private Function<T, List<T>> childrenFun;

    private List<T> treeList;

    private LinkedHashMap<Field, Pair<ExcelNode, Function<T, String>>> fieldMap = new LinkedHashMap<>();

    private List<Field> sortedFieldList = new ArrayList<>();

    private Integer maxDepth = 0;

    private Pair<String, List<Tree<String>>> header = Pair.of("", new ArrayList<>());

    private Integer headerHigh = 0;

    private Integer indent = 3;

    private String headerBgColor = "#FFFFFF";

    private short headerFontSize = 16;

    private Integer headerRowHeight = 20;

    private Integer dataRowHeight = 20;

    private LinkedList<ExcelNode> columnList = new LinkedList<>();

    /**
     * excel表要合并的区域列表
     */
    private List<List<Integer>> mergeAreas = new ArrayList<>();

    /**
     * excel表要渲染的数据
     */
    private List<List<String>> resTableData = new ArrayList<>();

    public void exportExcel(HttpServletResponse response, String fileName) {
        if (CollUtil.isEmpty(resTableData)) {
            renderTableData();
        }
        try (final XSSFWorkbook workbook = new XSSFWorkbook()) {
            final XSSFSheet sheet = workbook.createSheet();
            final XSSFDataFormat dataFormat = workbook.createDataFormat();
            sheet.setDefaultColumnWidth(20);
            sheet.setDefaultRowHeightInPoints(20);
            final CellStyle headerStyle = getHeaderStyle(workbook);
            for (int i = 0; i < headerHigh; i++) {
                final Row row = sheet.createRow(i);
                row.setHeightInPoints(headerRowHeight);
                final List<String> rowData = resTableData.get(i);
                for (int j = 0; j < rowData.size(); j++) {
                    final Cell cell = row.createCell(j);
                    cell.setCellValue(rowData.get(j));
                    cell.setCellStyle(headerStyle);
                }
            }
            final List<CellStyle> cellStyleList = getCellStyleList(workbook, dataFormat);
            for (int i = headerHigh; i < resTableData.size(); i++) {
                final XSSFRow row = sheet.createRow(i);
                row.setHeightInPoints(dataRowHeight);
                final List<String> rowData = resTableData.get(i);
                for (int j = 0; j < rowData.size(); j++) {
                    final Cell cell = row.createCell(j);
                    // 树形节点展示的数据样式，默认以 右边第一例的样式为准
                    int styleIndex = Math.max(j - maxDepth + 1, 0);
                    final CellStyle cellStyle = CollUtil.get(cellStyleList, styleIndex);
                    final Field field = sortedFieldList.get(styleIndex);
                    final String v = rowData.get(j);
                    final Object value = getValue(field, v, cellStyle, dataFormat);
                    CellUtil.setCellValue(cell, value, cellStyle);
                }
            }
            dealTableColumnWidth(sheet);
            // 合并单元格
            mergeAreas.stream().filter(area -> CollUtil.size(area) == 4)
                    .filter(area -> area.get(2) > 1 || area.get(3) > 1)
                    .map(area -> {
                        final Integer rowIndex = area.get(0);
                        final Integer colIndex = area.get(1);
                        final Integer width = area.get(2);
                        final Integer height = area.get(3);
                        return new CellRangeAddress(rowIndex, rowIndex + height - 1, colIndex, colIndex + width - 1);
                    }).distinct().forEach(sheet::addMergedRegion);
            // 设置文件名称
            response.setContentType(TextConst.EXCEL_CONTENT_TYPE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            response.setHeader(TextConst.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8));
            workbook.write(response.getOutputStream());
        } catch (Exception e) {
            throw TreeExportException.of( "导出Excel失败", e);
        }
    }

    private static Object getValue(Field field, String v, CellStyle cellStyle, XSSFDataFormat dataFormat) {
        Object o = Convert.convertQuietly(field.getType(), v);
        if (Objects.nonNull(o) && o instanceof BigDecimal b && ReUtil.isMatch("^-?\\d+(\\.\\d+)?%$", v)) {
            cellStyle.setDataFormat(dataFormat.getFormat("0.00%"));
            o = NumberUtil.div(b, 100);
        }
        return Opt.ofNullable(o).orElse(v);
    }

    private void dealTableColumnWidth(XSSFSheet sheet) {
        // 调整树形缩进
        if (indent > 0) {
            for (int i = 0; i < maxDepth - 1; i++) {
                sheet.setColumnWidth(i, indent * 256);
            }
        }
        // 调整数据列宽
        IntStream.range(maxDepth - 1, maxDepth + columnList.size() - 2).forEach(i -> {
            final ExcelNode excelNode = CollUtil.get(columnList, i);
            if(Objects.nonNull(excelNode)){
                sheet.setColumnWidth(i, excelNode.width() * 256);
            }
        });
    }

    private List<CellStyle> getCellStyleList(XSSFWorkbook workbook, XSSFDataFormat dataFormat) {
        final ArrayList<CellStyle> list = new ArrayList<>();
        for (int i = 0; i < columnList.size(); i++) {
            ExcelNode col = i==0 ? columnList.get(1): columnList.get(i);
            final XSSFCellStyle cellStyle = workbook.createCellStyle();
            Field colField = sortedFieldList.get(i);
            if(colField.getType() == LocalDate.class ||
                    colField.getType() == LocalDateTime.class ||
                    colField.getType() == LocalTime.class || colField.getType() == Date.class){
                // 日期格式化
                cellStyle.setDataFormat(dataFormat.getFormat(col.dateFormat()));
            }
            cellStyle.setBorderTop(BorderStyle.THIN);
            cellStyle.setBorderBottom(BorderStyle.THIN);
            cellStyle.setBorderLeft(BorderStyle.THIN);
            cellStyle.setBorderRight(BorderStyle.THIN);
            // 垂直居中
            cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            // 自动换行
            cellStyle.setWrapText(col.wrapText());
            // 设置背景色
            if (StringUtils.isNotBlank(col.bgColor()) && ReUtil.isMatch("^#[0-9a-fA-F]{6}$", col.bgColor())) {
                final XSSFColor xssfColor = new XSSFColor(ColorUtil.getColor(col.bgColor()), null);
                cellStyle.setFillForegroundColor(xssfColor);
                cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            }
            // 设置字体大小
            if (col.fontSize() > 0) {
                final Font font = workbook.createFont();
                font.setBold(col.bold());
                font.setFontHeightInPoints(col.fontSize());
                cellStyle.setFont(font);
            }
            // 设置水平位置
            cellStyle.setAlignment(col.align());
            list.add(cellStyle);
        }
        if(CollUtil.isNotEmpty(list)){
            // 强制左对齐
            list.get(0).setAlignment(HorizontalAlignment.LEFT);
        }
        return list;
    }

    private CellStyle getHeaderStyle(XSSFWorkbook workbook) {
        final XSSFCellStyle headerStyle = workbook.createCellStyle();
        headerStyle.setAlignment(HorizontalAlignment.CENTER);
        final Font headerFont = workbook.createFont();
        headerFont.setBold(true);
        headerFont.setFontHeightInPoints(headerFontSize);
        headerStyle.setFont(headerFont);
        headerStyle.setBorderTop(BorderStyle.THIN);
        headerStyle.setBorderBottom(BorderStyle.THIN);
        headerStyle.setBorderLeft(BorderStyle.THIN);
        headerStyle.setBorderRight(BorderStyle.THIN);
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        final XSSFColor xssfColor = new XSSFColor(ColorUtil.getColor(headerBgColor), null);
        headerStyle.setFillForegroundColor(xssfColor);
        // 垂直居中
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 自动换行
        headerStyle.setWrapText(true);
        return headerStyle;
    }


    public List<List<String>> renderTableData() {
        if (CollUtil.isNotEmpty(resTableData)) {
            return resTableData;
        }
        initHeader();
        final List<List<String>> headerList = transHeaderList();
        final List<List<String>> dataList = transDataList();
        resTableData.addAll(headerList);
        resTableData.addAll(dataList);
        return resTableData;
    }

    private void initHeader() {
        final List<ExcelNode> list = fieldMap.values().stream().map(Pair::getKey)
                .sorted(Comparator.comparingInt(ExcelNode::index)).toList();
        final List<ExcelNode> labelNodes = list.stream().filter(excelNode -> Boolean.TRUE.equals(excelNode.isTreeLabel())).toList();
        if (labelNodes.size() > 1) {
            throw new TreeExportException("树形Excel标签节点不能超过一个");
        }
        ExcelNode labelNode = CollUtil.getFirst(labelNodes);
        if(Objects.nonNull(labelNode)){
            indent = labelNode.indent();
            headerBgColor = labelNode.bgColor();
            headerFontSize = labelNode.fontSize();
            headerRowHeight = labelNode.height();
            dataRowHeight = labelNode.dataRowHeight();
        }
        final List<ExcelNode> nodes = list.stream().filter(excelNode -> Boolean.FALSE.equals(excelNode.isTreeLabel())).toList();
        final Tree<String> root = new Tree<>();
        final List<List<String>> nameList = nodes.stream().map(ExcelNode::name).map(Arrays::asList).toList();
        nameList.forEach(names -> {
            Tree<String> parent = root;
            for (String name : names) {
                final Tree<String> node = new Tree<>();
                node.setId(name);
                parent.addChildren(node);
                parent = node;
            }
        });
        headerHigh = nameList.stream().map(List::size).max(Integer::compare).orElse(1);
        header = Pair.of(Objects.nonNull(labelNode)?String.join("/", labelNode.name()):"", root.getChildren());
    }

    private List<List<String>> transHeaderList() {
        final String treeHeaderLabel = header.getKey();
        final List<Tree<String>> trees = header.getValue();
        final List<List<String>> res = IntStream.range(0, headerHigh)
                .boxed().map(i -> {
                    final List<String> one = new ArrayList<>();
                    IntStream.range(0, maxDepth).forEach(j -> one.add(treeHeaderLabel));
                    return one;
                }).toList();
        // 表头左上角合并区域row+col+宽度+高度
        mergeAreas.add(Arrays.asList(0, 0, maxDepth, headerHigh));
        renderHeader(trees, res, 0);
        return res;
    }

    private void renderHeader(List<Tree<String>> trees, List<List<String>> res, int level) {
        if (CollUtil.isEmpty(trees)) {
            return;
        }
        for (Tree<String> tree : trees) {
            final int width = getTreeNodeLeafNum(tree);
            if (width == 1) {
                // 证明此节点为叶子节点，要撑起高度
                final int col = res.get(level).size();
                mergeAreas.add(Arrays.asList(level, col, 1, Math.max(1, headerHigh - level)));
                for (int i = level; i < headerHigh; i++) {
                    final List<String> row = res.get(i);
                    row.add(tree.getId());
                }
            } else {
                // 非叶子节点要撑起宽度
                final List<String> one = res.get(level);
                mergeAreas.add(Arrays.asList(level, one.size(), width, 1));
                for (int j = 0; j < width; j++) {
                    one.add(tree.getId());
                }
            }
            renderHeader(tree.getChildren(), res, level + 1);
        }

    }

    private int getTreeNodeLeafNum(Tree<String> node) {
        return getTreeNodeLeafNum(List.of(node));
    }

    private int getTreeNodeLeafNum(List<Tree<String>> nodeList) {
        if (CollUtil.isEmpty(nodeList)) {
            return 0;
        }
        final List<Tree<String>> leafNodeList = nodeList.stream().filter(tree -> CollUtil.isEmpty(tree.getChildren())).toList();
        int res = 0;
        for (Tree<String> t : nodeList) {
            res += getTreeNodeLeafNum(t.getChildren());
        }
        return res + leafNodeList.size();
    }

    private List<List<String>> transDataList() {
        if (CollUtil.isEmpty(treeList) || Objects.isNull(childrenFun)) {
            return Collections.emptyList();
        }
        final List<List<String>> dataList = new ArrayList<>();
        final TreeMap<Field, Pair<ExcelNode, Function<T, String>>> sortedFieldMap = MapUtil.sort(fieldMap, (k1, k2) -> fieldMap.get(k1).getKey().index() - fieldMap.get(k2).getKey().index());
        sortedFieldMap.forEach((field, pair) -> {
            columnList.add(pair.getKey());
            sortedFieldList.add(field);
        });

        renderDateList(treeList, sortedFieldMap, 0, dataList);
        return dataList;
    }

    private void renderDateList(List<T> list, Map<Field, Pair<ExcelNode, Function<T, String>>> sortedFieldMap, int depth, List<List<String>> dataList) {
        if (CollUtil.isEmpty(list)) {
            return ;
        }for (T t : list) {
            final List<String> data = new ArrayList<>();
            sortedFieldMap.forEach((field, pair) -> {
                final ExcelNode excelNode = pair.getKey();
                final Function<T, String> func = pair.getValue();
                String endValue;
                if (Objects.isNull(func)) {
                    final Object value = ReflectUtils.invokeGetter(t, field);
                    endValue = defaultTrans(value, field);
                } else {
                    endValue = func.apply(t);
                }
                if (Boolean.TRUE.equals(excelNode.isTreeLabel())) {
                    // 树形标签
                    IntStream.range(0, depth).forEach(i -> data.add(""));
                    IntStream.range(depth, maxDepth).forEach(i -> data.add(endValue));
                } else {
                    data.add(endValue);
                }

            });
            dataList.add(data);
            // 添加合并区域
            final int row = headerHigh + dataList.size() - 1;
            mergeAreas.add(Arrays.asList(row, 0, depth, 1));
            mergeAreas.add(Arrays.asList(row, depth, maxDepth - depth, 1));
            // 子节点
            renderDateList(childrenFun.apply(t), sortedFieldMap, depth + 1, dataList);
        }
    }


    private String defaultTrans(Object o, Field field) {
        final ExcelNode excelNode = AnnotationUtil.getAnnotation(field, ExcelNode.class);
        if (o instanceof JSONNull || Objects.isNull(o)) {
            return Objects.nonNull(excelNode) ? excelNode.defaultValue() : "";
        } else if (o instanceof BigDecimal b) {
            return b.toPlainString();
        } else if (o instanceof Number n) {
            return String.valueOf(n);
        } else if (o instanceof String s) {
            return s.trim();
        } else if (o instanceof Boolean b) {
            return Boolean.TRUE.equals(b) ? "是" : "否";
        } else if (o instanceof Enum<?> e) {
            return e.name();
        } else if (o instanceof Character c) {
            return String.valueOf(c);
        } else if (o instanceof byte[] b) {
            return new String(b);
        } else if (o instanceof Date date) {
            return Objects.nonNull(excelNode) ? DateUtil.format(date, excelNode.dateFormat()) :
                    DateUtil.format(date, DatePattern.NORM_DATETIME_PATTERN);
        }
        return o.toString();
    }

    public static <T> Builder<T> builder(Class<T> clazz) {
        return new Builder<>(clazz);
    }

    public static class Builder<E> {
        private final Type type;

        private final ExcelTreeUtil<E> excelTreeUtils = new ExcelTreeUtil<>();

        private final Map<String, Function<E, String>> transMap = new HashMap<>();

        public Builder(Class<E> clazz) {
            type = clazz;
            excelTreeUtils.setType(clazz);
        }

        public Builder<E> data(E data) {
            excelTreeUtils.setTreeList(List.of(data));
            return this;
        }

        public Builder<E> data(List<E> data) {
            excelTreeUtils.setTreeList(data);
            return this;
        }

        public Builder<E> childrenFun(Function<E, List<E>> childrenFun) {
            excelTreeUtils.setChildrenFun(childrenFun);
            return this;
        }

        public Builder<E> trans(String fieldName, Function<E, String> transFun) {
            if (Objects.isNull(transFun) || StringUtils.isBlank(fieldName)) {
                return this;
            }
            final Class<?> clazz = TypeUtil.getClass(type);
            if (Objects.isNull(clazz)) {
                return this;
            }
            transMap.put(fieldName, transFun);
            return this;
        }
        public Builder<E> trans(Func1<E, ?> getter, Function<E, String> transFun) {
            final String fieldName = LambdaUtil.getFieldName(getter);
            return trans(fieldName, transFun);
        }

        public ExcelTreeUtil<E> build() {
            final Class<?> clazz = TypeUtil.getClass(type);
            if (Objects.isNull(clazz)) {
                throw new TreeExportException("树形Excel类型解析失败");
            }
            final Field[] fields = ReflectUtil.getFields(clazz, field -> AnnotationUtil.hasAnnotation(field, ExcelNode.class));
            final Map<Field, Pair<ExcelNode, Function<E, String>>> map = excelTreeUtils.getFieldMap();
            Stream.of(fields).forEach(field -> map.put(field, Pair.of(AnnotationUtil.getAnnotation(field, ExcelNode.class), transMap.get(field.getName()))));
            // 计算最大深度
            excelTreeUtils.setMaxDepth(computeMaxDepth(excelTreeUtils.getTreeList(), excelTreeUtils.getChildrenFun(), 0));
            return excelTreeUtils;
        }

        private Integer computeMaxDepth(List<E> treeList, Function<E, List<E>> childrenFun, Integer depth) {
            if (CollUtil.isEmpty(treeList) || Objects.isNull(childrenFun)) {
                return depth;
            }
            return treeList.stream().map(e -> {
                final List<E> children = childrenFun.apply(e);
                return computeMaxDepth(children, childrenFun, depth + 1);
            }).max(Integer::compareTo).orElse(depth);
        }
    }
}
